package com.zy.ems.common.util;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.Random;

import org.apache.commons.codec.digest.DigestUtils;

import com.zy.ems.common.cache.MemcachedUtils;

/**
 * 生成验证码工具类。
 * 
 * @author Luffy
 *
 */
public class ValidatorCodeUtil {
    /**
     * 设置备选验证码:包括"a-z"和数字"0-9"
     */
    //
    // private static final String BASE =
    // "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    // 这版只提供纯数字的验证码
    private static final String BASE = "0123456789";

    /**
     * 默认的图形验证码的高度
     */
    private static final int DEFAULT_WIDTH = 80;

    /**
     * 默认的图形验证码的宽度
     */
    private static final int DEFAULT_HEIGHT = 30;

    /**
     * 默认的验证码缓存失效周期，2分钟
     */
    private static final long DEFAULT_EXXPIRY = 1000 * 60 * 2L;

    /**
     * 验证码缓存Key的前缀
     */
    private static final String KEY_PREFIX = "VCODE_";

    /**
     * 验证码加密后缀
     */
    private static final String ENCRYPT_SUFFIX = "GZM";

    private ValidatorCodeUtil() {

    }

    /**
     * 生成指定位数的验证码，若位数<=0则默认生成4为验证码。
     * 
     * @param codeLength
     *            验证码长度
     * @return ValidatorCode
     */
    public static ValidatorCode createVCode(Dimension dimension) {
        // 生成验证码文本
        String vcode = createStrVCode(dimension.getCodeLength());
        BufferedImage image = null;
        // 判断是否生成图形验证码
        if (dimension.isCreateImage()) {
            // 生成图形验证码
            image = createImageVCode(vcode, dimension.width, dimension.height);
        }
        // 封装验证码
        return wrapVCode(vcode, image);
    }

    /**
     * 以密文的方式保存验证码。
     * 
     * @param wrapper
     *            验证码封装对象
     * @param dimension
     *            请求维度
     * @return boolean 是否保存成功
     */
    public static boolean saveVCode(ValidatorCode wrapper,
            Dimension dimension) {
        // 以映射key和密文生成缓存中的key
        String vcodeKey = createKey(dimension.key, wrapper.getCipherText());
        // 缓存验证码密文
        return MemcachedUtils.add(vcodeKey, wrapper.getCipherText(),
                DEFAULT_EXXPIRY);
    }

    /**
     * 删除验证码 <br/>
     *
     * @param key
     * @param cipherText
     * @return
     * @author sunshine
     * @date 2016年1月18日 下午2:44:40
     */
    public static boolean delVCode(String key, String cipherText) {
        // 以映射key和密文生成缓存中的key
        String vcodeKey = createKey(key, cipherText);
        return MemcachedUtils.delete(vcodeKey);
    }

    /**
     * 校验验证码的有效性。
     * 
     * @param key
     *            请求映射key
     * @param vcode
     *            验证码/密文
     * @return
     */
    public static boolean verifyVCode(String key, String vcode) {
        // 校验有效性：验证码的密文形式
        if (!verifyCipherText(key, vcode)) {
            // 直接验证码转密文进行校验
            String cipherText = encryptVCode(vcode);
            return verifyCipherText(key, cipherText);
        }
        return true;
    }

    /**
     * 校验验证码的有效性。
     * 
     * @param key
     *            请求映射key
     * @param cipherText
     *            密文
     * @return
     */
    private static boolean verifyCipherText(String key, String cipherText) {
        // 生成缓存中的key
        String vcodeKey = createKey(key, cipherText);
        // 缓存中的验证码
        String vcodeCache = String.class
                .cast(MemcachedUtils.getValue(vcodeKey));
        // 校验有效性：验证码的密文形式
        if (StringUtils.isNotEmpty(cipherText)
                && cipherText.equalsIgnoreCase(vcodeCache)) {
            return true;
        }
        return false;
    }

    /**
     * 生成验证码缓存的Key.
     * 
     * @param key
     *            请求映射key
     * @param code
     *            验证码/密文
     * @return
     */
    private static String createKey(String key, String code) {
        return KEY_PREFIX + key + code;
    }

    /**
     * 生成指定位数的验证码，若位数<=0则默认生成4为验证码。
     * 
     * @param codeLength
     *            验证码长度
     * @return String
     */
    private static String createStrVCode(int codeLength) {
        // 默认生成4位验证码
        if (codeLength <= 0) {
            codeLength = 4;
        }
        // randomCode用于保存随机产生的验证码
        StringBuffer randomCode = new StringBuffer();
        // 创建一个随机数生成器类
        Random random = new Random();
        // 随机产生codeLength位数字的验证码
        for (int i = 0; i < codeLength; i++) {
            int start = random.nextInt(BASE.length());
            String strRand = BASE.substring(start, start + 1);
            // 将产生的随机数组合在一起
            randomCode.append(strRand);
        }
        return randomCode.toString();
    }

    /**
     * 生成指定位数和高、宽的图形验证码，若位数<=0则默认生成4为验证码。
     * 若未对图形验证码的高或宽进行限制，则生成默认高(80)宽(30)的图形验证码。
     * 
     * @param vcode
     *            验证码
     * @param width
     *            图片的宽度
     * @param height
     *            图片的高度
     * @return 验证码封装对象
     */
    private static BufferedImage createImageVCode(String vcode, int width,
            int height) {
        // 生成默认高(80)宽(30)的图形验证码
        if (width <= 0 || height <= 0) {
            width = DEFAULT_WIDTH;
            height = DEFAULT_HEIGHT;
        }
        // 验证码图形流
        BufferedImage buffImg = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
        Graphics2D g = buffImg.createGraphics();
        // 设定图像背景色(因为是做背景，所以偏淡)
        // g.setColor(Color.WHITE);
        g.fillRect(0, 0, width, height);
        // 创建字体，字体的大小应该根据图片的高度来定
        Font font = new Font("微软雅黑", Font.HANGING_BASELINE, 28);
        // 设置字体
        g.setFont(font);

        // 创建一个随机数生成器类
        Random random = new Random();

        for (int i = 0; i < vcode.length(); i++) {
            // 用随机产生的颜色将验证码绘制到图像中
            // 生成随机颜色(因为是做前景，所以偏深)
            g.setColor(getRandColor(1, 100));

            // 调用函数出来的颜色相同，可能是因为种子太接近，所以只能直接生成
            g.setColor(new Color(20 + random.nextInt(110),
                    20 + random.nextInt(110), 20 + random.nextInt(110)));
            g.drawString(vcode.substring(i, i + 1), 15 * i + 6, 24);
        }
        return buffImg;
    }

    /**
     * 给定范围获得随机颜色
     * 
     * @param fc
     * @param bc
     * @return
     */
    private static Color getRandColor(int fc, int bc) {
        Random random = new Random();
        if (fc > 255) {
            fc = 255;
        }
        if (bc > 255) {
            bc = 255;
        }
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }

    /**
     * 封装成验证码对象。
     * 
     * @param vcode
     *            验证码文本
     * @param buffImg
     *            图形验证码
     * @return ValidatorCode
     */
    private static ValidatorCode wrapVCode(String vcode,
            BufferedImage buffImg) {
        ValidatorCode code = new ValidatorCode();
        code.code = vcode;
        code.image = buffImg;
        code.cipherText = encryptVCode(vcode);
        return code;
    }

    /**
     * 生成验证码密文。 规则：将vcode进行MD5加密，生成的MD5串拼接加密后缀ENCRYPT_SUFFIX，将拼接后的串再MD5加密。
     * 
     * @param vcode
     * @return
     */
    private static String encryptVCode(String vcode) {
        StringBuffer sbf = new StringBuffer();
        // 第一次MD5加密
        sbf.append(DigestUtils.md5Hex(vcode));
        // 拼接ENCRYPT_SUFFIX
        sbf.append(ENCRYPT_SUFFIX);
        // 第二次MD5加密
        return DigestUtils.md5Hex(sbf.toString());
    }

    /**
     * 验证码封装类
     */
    public static class ValidatorCode {

        /**
         * 验证码文本
         */
        private String code;

        /**
         * 图形验证码
         */
        private BufferedImage image;

        /**
         * 验证码密文
         */
        private String cipherText;

        /**
         * 验证码
         */
        public String getCode() {
            return code;
        }

        /**
         * 图片流
         */
        public BufferedImage getImage() {
            return image;
        }

        /**
         * 验证码密文
         */
        public String getCipherText() {
            return cipherText;
        }
    }

    /**
     * 请求验证码的维度定义。
     * 
     * @author Luffy
     *
     */
    public static class Dimension {

        public Dimension(String key) {
            this.key = key;
        }

        /**
         * 验证码映射的key
         */
        private String key;

        /**
         * 验证码长度
         */
        private int codeLength;

        /**
         * 是否创建图形验证码
         */
        private boolean isCreateImage;

        /**
         * 图形验证码的宽度
         */
        private int width;

        /**
         * 图形验证码的高度
         */
        private int height;

        public String getKey() {
            return key;
        }

        public boolean isCreateImage() {
            return isCreateImage;
        }

        public Dimension createImage(boolean isCreateImage) {
            this.isCreateImage = isCreateImage;
            return this;
        }

        public int getCodeLength() {
            return codeLength;
        }

        public Dimension codeLength(int codeLength) {
            this.codeLength = codeLength;
            return this;
        }

        public int getWidth() {
            return width;
        }

        public Dimension width(int width) {
            this.width = width;
            return this;
        }

        public int getHeight() {
            return height;
        }

        public Dimension height(int height) {
            this.height = height;
            return this;
        }

    }

}
